﻿
module StochasticSimulation.GibsonBruckAlgorithm.Tests

open System
open StochasticSimulation.ChemicalReaction.ChemicalReaction
open StochasticSimulation.ChemicalReaction.ChemicalSpecies
open StochasticSimulation.GibsonBruckAlgorithm
open StochasticSimulation.ChemicalReaction.PriorityQueue
open NUnit.Framework

[<TestFixture>]
type ``Gibson-Bruck Algorithm Tests``() = class

    let isInfinite nt =
        match nt with
        | Normal(t)    -> (false,t)
        | Infinite(t)  -> (true,t)

    let rnd = new Random()
    let calculate = calculateStoredTime rnd

    [<Test>]
    member self.`` calculate next time when propensity drops to zero.``() =
        let now = 0.05
        let oldTime = 0.1
        let oldNextTime = Normal(oldTime)
        let oldPropensity = 2.0
        let newPropensity = 0.0

        let (isInf,newTime) = isInfinite (calculate (oldPropensity,newPropensity) oldNextTime false now)

        Assert.True (isInf )
        Assert.AreEqual ( newTime,  oldPropensity * (oldTime - now) )

    [<Test>]
    member self.`` calculate next time when propensity returns from zero.``() =
        let now = 0.1

        let before = 0.01
        let lastTime = 0.2
        
        let oldPropensity = 2.0
        let newPropensity = 0.5
        
        let oldTime = Infinite( oldPropensity * (lastTime - before) )

        let (isInf,newTime) = isInfinite (calculate (oldPropensity,newPropensity) oldTime false now)
        
        Assert.False( isInf )
        Assert.AreEqual( newTime, (oldPropensity / newPropensity) * (lastTime - before) + now )


    [<Test>]
    member self.`` calculate next time when propensity does not reach zero.``() =
        let now = 0.1

        let oldPropensity = 3.0
        let newPropensity = 2.0

        let oldTime = 0.3
        let nextTime = Normal(0.3)

        let (isInf,newTime) = isInfinite (calculate (oldPropensity,newPropensity) nextTime false now)
        
        Assert.False( isInf )

        Assert.AreEqual( newTime, (oldPropensity/newPropensity)*(oldTime-now) + now )
        
    // could improve coverage of this test using FsCheck
    [<Test>]
    member self.`` calculate next reaction time for sure reaction.``() =
        let now = 0.1
        let randomNumber:double = 0.866
        let propensity = 3.0

        let rnd = new Random(100);
        let randomNumber = 0.9687746889

        let (inf,reactionTime) = isInfinite (calculateStoredTime rnd (0.0,propensity) (Normal(0.0)) true now )

        Assert.False(inf)
        Assert.That( reactionTime, Is.EqualTo( now + ( System.Math.Log(1.0/randomNumber ) / propensity ) ).Within( 0.00001 ) )
      
    [<Test>]
    member self.`` calculate next reaction time for sure reaction that has used up all depends.``() =
        let now = 0.1
        let randomNumber:double = 0.0
        let propensity = 0.0

        let oldPropensity = 3.0
        let oldNextTime = Normal(0.5)

        let rnd = null;

        let (inf,reactionTime) = isInfinite (calculateStoredTime rnd (oldPropensity,propensity) oldNextTime true now  )

        Assert.True(inf)
        Assert.AreEqual( reactionTime,  oldPropensity * (0.5 - now) )
end